﻿using System;
#if SERVER
using System.Diagnostics.Contracts;
#endif
using System.IO;
using System.Runtime.Serialization;
using System.Text;
using System.Xml;

namespace Ping.Core
{
	/// <summary>
	/// The container that stores a single data object that can be any of the known types (mostly Result and Request).
	/// It implements methods to serialize and deserialize the object to/from a stream.
	/// </summary>
	public static class Paket
	{
#if SERVER
		/// <summary>
		/// Reads a Data-packet from the given stream.
		/// </summary>
		/// <param name="reader"></param>
		/// <returns></returns>
		public static Data Read(BinaryReader reader)
		{
			Int32 length = reader.ReadInt32();
			return Helper.Deserialize(reader.BaseStream, length);
		}

		/// <summary>
		/// Writes the given data as a packet to the stream.
		/// </summary>
		/// <param name="writer"></param>
		/// <param name="data"></param>
		public static void Write(BinaryWriter writer, Data data)
		{
			var buffer = Helper.Serialize(data, Encoding.UTF8);
			writer.Write(buffer.Length);
			writer.Write(buffer);
		}
#endif

		public static byte[] Create(Data data)
		{
			return Helper.Serialize(data, Encoding.UTF8);
		}
	}

	public class Helper
	{
				/// <summary>
		/// The list of known types for the data contract serializer
		/// </summary>
		public static readonly Type[] KnownTypes = {
		                                           	typeof (Data),
		                                           	typeof (Request),
		                                           	typeof (Result),
		                                           	typeof (StatusResult),
		                                           	typeof (DnsRequest),
		                                           	typeof (DnsResult),
		                                           	typeof (PingRequest),
		                                           	typeof (PingResult),
		                                           	typeof (TracerouteRequest),
		                                           	typeof (TracerouteResult),
		                                           	typeof (GeoRequest),
		                                           	typeof (GeoResult)
		                                           };

		public Data Data;

		public Helper()
		{
		}

		public Helper(Data inner)
		{
			Data = inner;
		}

		/// <summary>
		/// Serializes the given result into an xml document and serializes that one
		/// into a bytestream using the given encoding.
		/// </summary>
		/// <param name="data"></param>
		/// <param name="encoding"></param>
		/// <returns></returns>
		public static byte[] Serialize(Data data, Encoding encoding)
		{
			if (data == null || encoding == null)
				throw new ArgumentNullException();

			var tmpStream = new MemoryStream();
			var settings = new XmlWriterSettings {Encoding = encoding};
			using (XmlWriter writer = XmlWriter.Create(tmpStream, settings))
			{
				var serialzer = new DataContractSerializer(typeof (Helper), KnownTypes);
				serialzer.WriteObject(writer, new Helper {Data = data});
			}

			return tmpStream.ToArray();
		}

		/// <summary>
		/// Deserializes the given stream into a result object, if possible.
		/// </summary>
		/// <param name="stream">The stream to read from</param>
		/// <param name="length">The length of the paket</param>
		/// <returns>The deserialized paket</returns>
		/// <exception cref="ReadRequestException">In case the request was malformed</exception>
		/// <exception cref="Exception">In any other case (or a subclass thereof)</exception>
		public static Data Deserialize(Stream stream, int length)
		{
#if SERVER
			Contract.Requires<ReadRequestException>(stream != null);
			Contract.Requires<ReadRequestException>(length > 0);
#else
			if (stream == null)
				throw new ArgumentNullException();
			if (length <= 0)
				throw new ArgumentException();
#endif

			try
			{
				var buffer = new byte[length];
				stream.Read(buffer, 0, length);

				var tmpStream = new MemoryStream(buffer);
				using (XmlReader reader = XmlReader.Create(tmpStream))
				{
					var serializer = new DataContractSerializer(typeof(Helper), KnownTypes);
					object result = serializer.ReadObject(reader);
					return ((Helper) result).Data;
				}
			}
			catch (SerializationException e)
			{
				throw new ReadRequestException(e);
			}
			catch (XmlException e)
			{
				throw new ReadRequestException(e);
			}
		}
	}
}